const { ccclass, property } = cc._decorator;
import baseManager from '../../../base/baseManager';
import baseView from '../../../base/baseView';

const BlockEvents = [
    'touchstart', 'touchmove', 'touchend', 'touchcancel',
    'mousedown', 'mousemove', 'mouseup',
    'mouseenter', 'mouseleave', 'mousewheel'
];

@ccclass
export default class uiManager<T extends string> extends baseManager {
    @property(cc.Prefab)
    private loadingPre: cc.Prefab = null;

    @property(cc.Prefab)
    private shadePre: cc.Prefab = null;

    private loading: cc.Node = null;
    private shade: cc.Node = null;
    private UI: cc.Node = null;

    private loadingCount: number = 0;

    private defaultUI: T = null;
    private defaultData: string = '';

    private prefabCache: any = {};
    private currPage: baseView = null;
    private currFocus: baseView = null;
    private touchEnabled: boolean = true;

    protected init(finish) {
        const setting: { static: string[], bundle: string } = cc.app.setting[this.managerName];
        if (setting.static) {
            setting.static.forEach((v, i) => {
                setting.static[i] = this.getUIPath(v);
            })
        }
        super.init(finish, setting.bundle);
    }

    protected onLoad() {
        const setting: { root: string } = cc.app.setting[this.managerName];
        const scene = cc.director.getScene();
        const canvas = scene.getComponentInChildren(cc.Canvas).node;
        for (var i = 0; i < BlockEvents.length; i++) {
            canvas.on(BlockEvents[i], this.stopPropagation, this, true);
        }

        this.UI = cc.find(setting.root);
        this.shade = cc.instantiate(this.shadePre);
        this.loading = cc.instantiate(this.loadingPre);
        this.shade.parent = this.UI;
        this.loading.parent = this.UI;
        this.shade.active = false;
        this.loading.active = false;
    }

    private stopPropagation(event: cc.Event) {
        // 直接设置node.forceTouch=true可以强制突破限制
        if (!this.touchEnabled && !event.target.forceTouch) {
            this.log(`触摸屏蔽`);
            event.stopPropagation();
        }
    }

    private showLoading() {
        ++this.loadingCount;
        this.loading.active = true;
    }

    private hideLoading() {
        if (this.loadingCount > 0 && --this.loadingCount === 0) {
            this.loading.active = false;
        }
    }

    /**
     * 获取前缀
     * @param {String} uiName 驼峰命名法，前缀小写。例如：popInfo，前缀为pop
     */
    private getPrefix(uiName: string): string {
        let children = this.UI.children;
        for (let index = 0; index < children.length; index++) {
            const uiRoot = children[index];
            if (uiName.indexOf(uiRoot.name.toLowerCase()) === 0) {
                return uiRoot.name.toLowerCase();
            }
        }
        return uiName;
    }

    /**
     * 获取后缀，并转小写
     * @param {String} uiName 驼峰命名法，前缀小写。例如：popInfo，前缀为info
     * @param {String} prefix [可选] 直接给出前缀
     */
    private getSuffix(uiName: string, prefix?: string): string {
        if (!prefix) {
            prefix = this.getPrefix(uiName);
        }
        return uiName.slice(prefix.length || 0).toLowerCase();
    }

    // 根据UI名字获取其父节点是谁
    private getUIParent(name: string) {
        let children = this.UI.children;
        for (let index = 0; index < children.length; index++) {
            const uiRoot = children[index];
            if (name.indexOf(uiRoot.name.toLowerCase()) === 0) {
                return uiRoot;
            }
        }
        this.error(`找不到${name}对应的UIRoot`);
        return children[1] || children[0] || this.UI;
    }

    // 根据UI名字获取场景内的节点
    private getUIInScene(name: string, multiple = false) {
        const parent = this.getUIParent(name);

        if (multiple) {
            return parent.children.filter(function (node) {
                return cc.isValid(node) && node.name === name;
            })
        } else {
            const node = parent.getChildByName(name);
            return cc.isValid(node) ? node : null;
        }
    }

    // 解析prefab
    private analyPrefab(prefab: cc.Prefab) {
        let node = cc.instantiate(prefab);
        node.active = false;
        node.parent = this.getUIParent(node.name);
        node.getComponent(cc.Widget)!.updateAlignment();
        return node;
    }

    // 根据UI名字获取UI路径
    private getUIPath(name: string) {
        const prefix = this.getPrefix(name);
        const suffix = this.getSuffix(name, prefix);
        return `${prefix}/${suffix}/view/${name}`;
    }

    // 获取UI节点
    private getUI(name: T, cb?: (node: cc.Node) => any) {
        if (!name) {
            cb && cb(null);
            return true;
        }

        // 判断是否已经存在节点并且是单例模式
        const nodes = this.getUIInScene(name, true) as cc.Node[];
        for (let index = 0; index < nodes.length; index++) {
            const node = nodes[index];
            if (node && node.getComponent(baseView).isSingleton === true) {
                cb && cb(node);
                return true;
            }
        }

        const isSync = this.load(name, (prefab) => {
            if (cb) {
                cb(prefab ? this.analyPrefab(prefab) : null) !== false && this.hideLoading();
            } else {
                this.hideLoading();
            }
        });

        // 动态加载
        if (!isSync) {
            this.showLoading();
        }

        return isSync;
    }

    /**
     * 预加载
     * @param {string} name ui名字
     * @param {(num:number,total:number)=>any} progress 
     * @param {(result: any)=>any} complete
     * @example
     * load(name, complete)
     * load(name, progress, complete)
     * 
     */
    public load(name: T | { path: T, type: typeof cc.Prefab }): boolean
    public load(name: T | { path: T, type: typeof cc.Prefab }, complete: (result: any) => any): boolean
    public load(name: T | { path: T, type: typeof cc.Prefab }, progress: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void, complete: (result: any) => any): boolean
    public load(name: T | { path: T, type: typeof cc.Prefab }, ...args: Function[]): boolean {
        const progress = (args[1] && args[0]) as (finish: number, total: number, item: cc.AssetManager.RequestItem) => void;
        const complete = (args[1] || args[0]) as (result: any) => any;

        // 验证name是否为真
        if (!name) {
            this.error(`load fail`);
            complete && complete(null);
            return true;
        }

        // 兼容setting中传入{path:'', type:cc.Prefab}结构
        let uiName = '';
        if (typeof name !== 'string') {
            uiName = name.path;
        } else {
            uiName = name;
        }

        // 判断有无缓存prefab
        let prefab = this.prefabCache[uiName];
        if (prefab) {
            complete && complete(prefab);
            return true;
        }

        // 异步加载
        const path = this.getUIPath(uiName);
        super.load({ path, type: cc.Prefab }, progress, (prefab) => {
            if (prefab) {
                this.prefabCache[uiName] = prefab;
                complete && complete(prefab);
            } else {
                complete && complete(null);
            }
        })

        return false;
    }

    /**
     * 销毁ui，释放ui的资源
     * @param {*} nameOrNodeOrCom 
     */
    public release(nameOrNodeOrCom: T | cc.Node | cc.Component | string) {
        let uiName: string = '';
        if (typeof nameOrNodeOrCom === 'string') {
            uiName = nameOrNodeOrCom;
        } else if (nameOrNodeOrCom != null && typeof nameOrNodeOrCom === 'object') {
            uiName = nameOrNodeOrCom instanceof cc.Node ? nameOrNodeOrCom.name : nameOrNodeOrCom.node.name;
        }

        if (!uiName) {
            this.error(`release ${nameOrNodeOrCom} fail`);
            return;
        }

        // 传入字符串是释放所有
        if (typeof nameOrNodeOrCom === 'string') {
            const nodes = this.getUIInScene(uiName, true) as cc.Node[];
            nodes.forEach(function (node) {
                if (!node || !cc.isValid(node)) return;
                node.parent = null;
                node.destroy();
            })
        } 
        // 传入节点或组件是释放单个
        else {
            const node = nameOrNodeOrCom instanceof cc.Node ? nameOrNodeOrCom : nameOrNodeOrCom.node;
            if (node && cc.isValid(node)) {
                node.parent = null;
                node.destroy();
            }
        }

        // 当全部释放时才清除缓存
        const nodes = this.getUIInScene(uiName, true) as cc.Node[];
        if (nodes.length === 0 || nodes.every(node => !cc.isValid(node))) {
            delete this.prefabCache[uiName];
            super.release(this.getUIPath(uiName), cc.Prefab);
        }
    }

    /**
     * 更新阴影的层级及显示
     */
    public refreshShade() {
        // 借助refreshShade实现onFocus、onLostFocus
        let onFocus = false;
        // 倒序遍历uiRoots
        let uiRoots = this.UI.children;
        for (let index = uiRoots.length - 1; index >= 0; index--) {
            const uiRoot = uiRoots[index];
            if (uiRoot !== this.shade && uiRoot !== this.loading) {
                // 倒序遍历uiRoot
                let children = uiRoot.children;
                for (let i = children.length - 1; i >= 0; i--) {
                    const node = children[i];
                    if (node !== this.shade) {
                        const com = node.getComponent(baseView);
                        // 触发onFocus
                        if (!onFocus && com.isCaptureFocus && com.isShowing) {
                            onFocus = true;
                            if (this.currFocus !== com) {
                                this.currFocus && this.currFocus.constructor.prototype.focus.call(this.currFocus, false);
                                this.currFocus = com;
                                this.currFocus.constructor.prototype.focus.call(this.currFocus, true);
                            }
                        }
                        // 添加遮罩
                        if (com.isNeedShade && com.isShowing) {
                            this.shade.parent = uiRoot;
                            this.shade.active = true;
                            this.shade.zIndex = node.zIndex;

                            let shadeIndex = this.shade.getSiblingIndex();
                            let nodeIndex = node.getSiblingIndex();
                            if (shadeIndex > nodeIndex) {
                                this.shade.setSiblingIndex(nodeIndex);
                            } else {
                                this.shade.setSiblingIndex(nodeIndex - 1);
                            }
                            return;
                        }
                    }
                }
            }
        }

        this.shade.active = false;
        if (!onFocus) {
            this.currFocus && this.currFocus.constructor.prototype.focus.call(this.currFocus, false);
            this.currFocus = null
        };
    }

    /**
     * 展示默认View
     * @param {*} param0 
     */
    public showDefault(onShow?: (result?: any) => any) {
        if (this.defaultUI) {
            this.show({
                name: this.defaultUI,
                data: this.defaultData,
                onShow
            })
        } else {
            onShow && onShow();
            this.warn(`defaultUI 不存在`);
        }
    }

    /**
     * 展示一个View
     * @param {*} param0 
     */
    public show({ name, data, onShow, onHide, onError, top = true }: { name: T, data?: any, onShow?: (result?: any) => any, onHide?: (result?: any) => any, onError?: (result?: string) => any, top?: boolean }): void {
        if (name.startsWith('control')) {
            name = <T>('top' + name.slice('control'.length));
        }

        const show = () => this.getUI(name, (node) => {
            if (!node) {
                this.error(`show ${name} 不存在或加载失败`);
                onError ? onError(`${name} 不存在或加载失败`) : this.scheduleOnce(show, 0.1);
                return onError ? true : false;
            }

            const com: baseView = node.getComponent(name);

            if (name.indexOf('page') >= 0) {
                node.setSiblingIndex(0);
                com.constructor.prototype.show.call(com,
                    data,
                    onShow,
                    onHide,
                    (res) => {
                        if (res) {
                            if (cc.isValid(this.currPage) && this.currPage !== com && this.currPage.isShowing) {
                                this.currPage.hide({ name, com });
                            }
                            this.currPage = com;
                        } else {
                            onError && onError('beforeShow');
                        }
                    }
                );
            } else {
                top && node.setSiblingIndex(-1);
                com.constructor.prototype.show.call(com, data, onShow, onHide);
            }
        });

        show();
    }

    /**
     * 刷新View
     * @param {*} param0 
     */
    public refresh({ name, data, onRefresh, top = false }: { name: T, data?: any, onRefresh?: (result?: any) => any, top?: boolean }): baseView | void {
        const nodes = this.getUIInScene(name, true) as cc.Node[];

        if (nodes.length === 0) {
            return this.error(`refresh ${name} 不存在`);
        }

        nodes.forEach(function (node) {
            top && node.setSiblingIndex(-1);
            const com: baseView = node.getComponent(name);
            com.constructor.prototype.refresh.call(com, data, onRefresh);
        })
    }

    /**
     * 移除View
     * @param {*} param0 
     */
    public hide({ name, data, onHide }: { name: T, data?: any, onHide?: (result?: any) => any }): baseView | void {
        const nodes = this.getUIInScene(name, true) as cc.Node[];

        if (nodes.length === 0) {
            return this.error(`refresh ${name} 不存在`);
        }

        nodes.forEach((node) => {
            const com: baseView = node.getComponent(name);

            if (this.currPage === com) {
                this.currPage = null;
            }

            com.hide(data, onHide);
        })
    }

    /**
     * 设置触摸是否启用
     * @param {*} enabled 
     */
    public setTouchEnabled(enabled: boolean) {
        this.touchEnabled = !!enabled;
    }

    /**
     * 震屏效果
     * 参数：duration 震屏时间
     * @param {*} param0 
     */
    public shakeEffect({ duration = 0, name, node, effectScale = 1, timeScale = 1, finish }: { duration: number, name?: T, node?: cc.Node, effectScale?: number, timeScale?: number, finish?: Function }) {
        if (duration <= 0) {
            return;
        }
        node = node || this.getUIInScene(name) as cc.Node || this.UI;
        const x = node.x;
        const y = node.y;
        const time = 0.02 * timeScale;

        const action = node.runAction(
            cc.repeatForever(
                cc.sequence(
                    cc.moveTo(time, cc.v2(x + 5 * effectScale, y + 7 * effectScale)),
                    cc.moveTo(time, cc.v2(x - 6 * effectScale, y + 7 * effectScale)),
                    cc.moveTo(time, cc.v2(x - 13 * effectScale, y + 3 * effectScale)),
                    cc.moveTo(time, cc.v2(x + 3 * effectScale, y - 6 * effectScale)),
                    cc.moveTo(time, cc.v2(x - 5 * effectScale, y + 5 * effectScale)),
                    cc.moveTo(time, cc.v2(x + 2 * effectScale, y - 8 * effectScale)),
                    cc.moveTo(time, cc.v2(x - 8 * effectScale, y - 10 * effectScale)),
                    cc.moveTo(time, cc.v2(x + 3 * effectScale, y + 10 * effectScale)),
                    cc.moveTo(time, cc.v2(x, y))
                )
            )
        );

        this.scheduleOnce(function () {
            node.stopAction(action);
            node.setPosition(x, y);
            finish && finish();
        }, duration);
    }
}